#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <iostream>
#include <cstdlib>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glut.h>
#endif

#include "Common.h"
#include "Textures.h"
using namespace std;

#include "Scene.h"

Pocket::Pocket(Vec3f _origin)
{
   origin = _origin;
   setColor(Vec3f(0,0,0));
   setSpecular(Vec3f(0,0,0));
   setShininess(0);   
}

bool Pocket::hit(Ball* b)
{
   Vec3f p = b->getPosition();
   double x = p[0] - origin[0];
   double z = p[2] - origin[2];
   double d = sqrt(x*x + z*z);
   
   if (d <= getRadius() - b->getRadius())
      return true;
   
   return false;
}

bool Pocket::near(Ball* b)
{
   Vec3f p = b->getPosition();
   double x = p[0] - origin[0];
   double z = p[2] - origin[2];
   double d = sqrt(x*x + z*z);
   
   if (d < getRadius() + 50)
      return true;
   
   return false;
}

SidePocket::SidePocket(Vec3f _origin) : Pocket(_origin) { }
CornerPocket::CornerPocket(Vec3f _origin) : Pocket(_origin) { }

void SidePocket::draw(bool ortho)
{
   glPushMatrix();
   IDrawableObject::draw(ortho);
   
   double r = Config::getSidePocketRadius();
   double rw = (origin[0] < 0) ? r : -r;

   Config::getTextures()->useTexture(TEXTURE_SIDE_POCKET);
   glBegin(GL_QUADS);
   glNormal3f(0.0f, +1.0f, 0.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(origin[0], 1, origin[2]-r);
   glTexCoord2f(0.0f,1.0f); glVertex3f(origin[0], 1, origin[2]+r);
   glTexCoord2f(1.0f,1.0f); glVertex3f(origin[0]+rw, 1, origin[2]+r);
   glTexCoord2f(1.0f,0.0f); glVertex3f(origin[0]+rw, 1, origin[2]-r);
   glEnd();    
   
   glDisable(GL_TEXTURE_2D); // Disable texturing   
   glPopMatrix();
}

void CornerPocket::draw(bool ortho)
{
   glPushMatrix();
   IDrawableObject::draw(ortho);
   
   double r = Config::getCornerPocketRadius();
   double rw = (origin[0] < 0) ? r : -r;
   double rd = (origin[2] < 0) ? r : -r;
    
   Config::getTextures()->useTexture(TEXTURE_CORNER_POCKET);
   glBegin(GL_QUADS);
   glNormal3f(0.0f, +1.0f, 0.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(origin[0], 1, origin[2]);
   glTexCoord2f(0.0f,1.0f); glVertex3f(origin[0], 1, origin[2]+rd);
   glTexCoord2f(1.0f,1.0f); glVertex3f(origin[0]+rw, 1, origin[2]+rd);
   glTexCoord2f(1.0f,0.0f); glVertex3f(origin[0]+rw, 1, origin[2]);
   glEnd();    
   
   glDisable(GL_TEXTURE_2D); // Disable texturing
   glPopMatrix();
}


Room::Room()
{
   
}

void Room::draw(bool ortho)
{
   glPushMatrix();
   glPushName(1);
   
   IDrawableObject::draw(ortho);
   
   double w = Config::getRoomWidth() / 2.0;
   double d = Config::getRoomDepth() / 2.0;
   double h = Config::getRoomHeight();
   double l = Config::getTableLift();
   
   
   // Enable texturing and select first texture
   glColor3f(1.0f,1.0f,1.0f);
   
   Config::getTextures()->useTexture(TEXTURE_FIRST_BALL);
   glBegin(GL_QUADS);
   glNormal3f(0.0f, 0.0f, 1.0f);
   glTexCoord2f(1.0f,0.0f); glVertex3f(-w, h-l, -d);
   glTexCoord2f(1.0f,1.0f); glVertex3f(-w,  -l, -d);
   glTexCoord2f(0.0f,1.0f); glVertex3f(+w,  -l, -d);
   glTexCoord2f(0.0f,0.0f); glVertex3f(+w, h-l, -d);
   glEnd();  
   
   Config::getTextures()->useTexture(TEXTURE_FIRST_BALL + 1);
   glBegin(GL_QUADS);
   glNormal3f(0.0f, 0.0f, -1.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(+w, h-l, d);
   glTexCoord2f(0.0f,1.0f); glVertex3f(+w,  -l, d);
   glTexCoord2f(1.0f,1.0f); glVertex3f(-w,  -l, d);
   glTexCoord2f(1.0f,0.0f); glVertex3f(-w, h-l, d);
   glEnd(); 
   
   Config::getTextures()->useTexture(TEXTURE_FIRST_BALL + 2);
   glBegin(GL_QUADS);
   glNormal3f(-1.0f, 0.0f, 0.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(+w, h-l, -d);
   glTexCoord2f(0.0f,1.0f); glVertex3f(+w,  -l, -d);
   glTexCoord2f(1.0f,1.0f); glVertex3f(+w,  -l, d);
   glTexCoord2f(1.0f,0.0f); glVertex3f(+w, h-l, d);
   glEnd(); 
   
   Config::getTextures()->useTexture(TEXTURE_FIRST_BALL + 4);
   glBegin(GL_QUADS);
   glNormal3f(1.0f, 0.0f, 0.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(-w, h-l, d);
   glTexCoord2f(0.0f,1.0f); glVertex3f(-w,  -l, d);
   glTexCoord2f(1.0f,1.0f); glVertex3f(-w,  -l, -d);
   glTexCoord2f(1.0f,0.0f); glVertex3f(-w, h-l, -d);
   glEnd(); 
   
   glDisable(GL_TEXTURE_2D); // Disable texturing
   
   //Config::getTextures()->useTexture(TEXTURE_TABLE_SIDES);
   
   double width = 700;
   double black = true;
   
   glRotatef(30, 0.0,1.0,0.0);
   
   for (double i = -w * 1.5; i < w*1.5 + width; i+=width)
   {
      for (double j = -w*1.5; j < w*1.5 + width; j+=width)
      {
         if (black == true)
         {
            glColor3f(0.0f, 0.0f, 0.0f);
            black = false;
         }
         else
         {
            glColor3f(1.0f, 1.0f, 1.0f);
            black = true;
         }
         glBegin(GL_QUADS);
         glNormal3f(0.0f, +1.0f, 0.0f);
         glVertex3f(i, -l, j);
         glVertex3f(i+width, -l, j);
         glVertex3f(i+width, -l, j+width);
         glVertex3f(i, -l, j+width);
         glEnd();          
      }      
   }
   

   glPopName();
   glPopMatrix();
}

TableSides::TableSides()
{
//   setColor(Vec3f(0,1,0));
//   setSpecular(Vec3f(0.2,0.2,0.2));
//   setShininess(2);   
}

void TableSides::draw(bool ortho)
{
   IDrawableObject::draw(ortho);
   
   double w = Config::getTableWidth() / 2.0 + 263.934;
   double d = Config::getTableDepth() / 2.0 + 276.923;
   double h = Config::getTableHeight();
   
   Config::getTextures()->useTexture(TEXTURE_TABLE_SIDES);
   glBegin(GL_QUADS);
   glNormal3f(0.0f, +1.0f, 0.0f);
   glTexCoord2f(0.0f,0.0f); glVertex3f(-w, h, -d);
   glTexCoord2f(1.0f,0.0f); glVertex3f(-w, h, d);
   glTexCoord2f(1.0f,1.0f); glVertex3f(+w, h, d);
   glTexCoord2f(0.0f,1.0f); glVertex3f(+w, h, -d);

   glEnd();    
   
   glDisable(GL_TEXTURE_2D); // Disable texturing
}

TableTop::TableTop()
{
   setColor(Vec3f(0,1,0));
   setSpecular(Vec3f(0,0,0));
   setShininess(0);
}


void TableTop::draw(bool ortho)
{
   IDrawableObject::draw(ortho);
   
   int precision = 100;
   
   double cX = Config::getTableWidth() / precision;
   double cZ = Config::getTableDepth() / precision;
   double halfWidth = Config::getTableWidth() / 2;
   double halfDepth = Config::getTableDepth() / 2;

   for (int j = 0; j < precision; j++)
   {
      for (int i = 0; i < precision; i++)
      {
   		glBegin(GL_QUADS);   
         glNormal3f(+0.0f, +1.0f,+0.0f);
         glVertex3f(i*cX - halfWidth, 0, j*cZ - halfDepth);
         glVertex3f(i*cX - halfWidth, 0, (j+1)*cZ - halfDepth);
         glVertex3f((i+1)*cX - halfWidth, 0, (j+1)*cZ - halfDepth);
         glVertex3f((i+1)*cX - halfWidth, 0, j*cZ - halfDepth);
         glEnd(); 
      }
   } 
   
   double height = Config::getTableHeight();
   double cY = Config::getTableHeight() / precision;
   double sR = Config::getSidePocketRadius();
   double cR = Config::getCornerPocketRadius();
      
   double indent = 120;
   double dip = 30;
   
   // left
   glBegin(GL_QUADS);   
   glNormal3f(+1.0f, +0.0f,+0.0f);
   glVertex3f(-halfWidth, 0, halfDepth - cR);
   glVertex3f(-halfWidth, 0, sR);
   glVertex3f(-halfWidth + indent, height - dip, sR);
   glVertex3f(-halfWidth + indent, height - dip, halfDepth - cR);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(-halfWidth + indent, height - dip, halfDepth - cR);
   glVertex3f(-halfWidth + indent, height - dip, sR);
   glVertex3f(-halfWidth, height, sR);
   glVertex3f(-halfWidth, height, halfDepth - cR);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(+1.0f, +0.0f,+0.0f);
   glVertex3f(-halfWidth, 0, -sR);
   glVertex3f(-halfWidth, 0, -halfDepth + cR);
   glVertex3f(-halfWidth + indent, height - dip, -halfDepth + cR);
   glVertex3f(-halfWidth + indent, height - dip, -sR);
   glEnd();
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(-halfWidth + indent, height - dip, -sR);
   glVertex3f(-halfWidth + indent, height - dip, -halfDepth + cR);
   glVertex3f(-halfWidth, height, -halfDepth + cR);
   glVertex3f(-halfWidth, height, -sR);
   glEnd(); 

   // right
   glBegin(GL_QUADS);   
   glNormal3f(-1.0f, +0.0f,+0.0f);
   glVertex3f(halfWidth, 0, halfDepth - cR);
   glVertex3f(halfWidth - indent, height - dip, halfDepth - cR);
   glVertex3f(halfWidth - indent, height - dip, sR);
   glVertex3f(halfWidth, 0, sR);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(halfWidth - indent, height - dip, halfDepth - cR);
   glVertex3f(halfWidth, height, halfDepth - cR);
   glVertex3f(halfWidth, height, sR);
   glVertex3f(halfWidth - indent, height - dip, sR);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(-1.0f, +0.0f,+0.0f);
   glVertex3f(halfWidth, 0, -sR);
   glVertex3f(halfWidth - indent, height - dip, -sR);
   glVertex3f(halfWidth - indent, height - dip, -halfDepth + cR);
   glVertex3f(halfWidth, 0, -halfDepth + cR);
   glEnd();
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(halfWidth - indent, height - dip, -sR);
   glVertex3f(halfWidth, height, -sR);
   glVertex3f(halfWidth, height, -halfDepth + cR);
   glVertex3f(halfWidth - indent, height - dip, -halfDepth + cR);
   glEnd(); 
   
   // back
   glBegin(GL_QUADS);   
   glNormal3f(0.0f, +0.0f,+1.0f);
   glVertex3f(-halfWidth + cR, 0, -halfDepth);
   glVertex3f(halfWidth - cR, 0, -halfDepth);
   glVertex3f(halfWidth - cR, height - dip, -halfDepth + indent);
   glVertex3f(-halfWidth + cR, height - dip, -halfDepth + indent);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(-halfWidth + cR, height - dip, -halfDepth + indent);
   glVertex3f(halfWidth - cR, height - dip, -halfDepth + indent);
   glVertex3f(halfWidth - cR, height, -halfDepth);
   glVertex3f(-halfWidth + cR, height, -halfDepth);
   glEnd(); 
   
   // front
   glBegin(GL_QUADS);   
   glNormal3f(0.0f, +0.0f,+1.0f);
   glVertex3f(-halfWidth + cR, 0, halfDepth);
   glVertex3f(-halfWidth + cR, height - dip, halfDepth - indent);
   glVertex3f(halfWidth - cR, height - dip, halfDepth - indent);
   glVertex3f(halfWidth - cR, 0, halfDepth);
   glEnd(); 
   
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(-halfWidth + cR, height - dip, halfDepth - indent);
   glVertex3f(-halfWidth + cR, height, halfDepth);
   glVertex3f(halfWidth - cR, height, halfDepth);
   glVertex3f(halfWidth - cR, height - dip, halfDepth - indent);
   glEnd(); 
   
   double l = Config::getTableLift();
   double w = Config::getTableWidth() / 2.0 + 263.934 - 10;
   double d = Config::getTableDepth() / 2.0 + 276.923 - 10;
   
   
   // sides down
   glColor3f(+0.0f, +0.0f,+0.0f);
   
   // hide ground under table
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glVertex3f(-halfWidth - 200, -1, -halfDepth - 200);
   glVertex3f(-halfWidth - 200, -1, halfDepth + 200);
   glVertex3f(halfWidth + 200, -1, halfDepth + 200);
   glVertex3f(halfWidth + 200, -1, -halfDepth - 200);
   glEnd();  
   
   double incline = 800;
   
   // front side

   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +0.0f,+1.0f);
   glVertex3f(-w, height, d);
   glVertex3f(-w+incline, -l, d-incline);
   glVertex3f(w-incline, -l, d-incline);
   glVertex3f(w, height, d);
   glEnd(); 

   // back side
   Config::getTextures()->useTexture(TEXTURE_FIRST_BALL);
   glBegin(GL_QUADS);   
   glNormal3f(+0.0f, +0.0f,-1.0f);
   glTexCoord2f(0.0f,0.0f);glVertex3f(-w, height, -d);
   glTexCoord2f(1.0f,0.0f);glVertex3f(w, height, -d);
   glTexCoord2f(1.0f,1.0f);glVertex3f(w - incline, -l, -d + incline);
   glTexCoord2f(0.0f,1.0f);glVertex3f(-w + incline, -l, -d + incline);
   glEnd(); 

   glDisable(GL_TEXTURE_2D); // Disable texturing
   
   // left side
   glBegin(GL_QUADS);   
   glNormal3f(-1.0f, +0.0f,+0.0f);
   glVertex3f(-w, height, -d);
   glVertex3f(-w+incline, -l, -d+incline);
   glVertex3f(-w+incline, -l, d-incline);
   glVertex3f(-w, height, d);
   glEnd();
   
   // right side
   glBegin(GL_QUADS);   
   glNormal3f(-1.0f, +0.0f,+0.0f);
   glVertex3f(w, height, -d);
   glVertex3f(w, height, d);
   glVertex3f(w-incline, -l, d-incline);
   glVertex3f(w-incline, -l, -d+incline);
   glEnd();  
   

}


Table::Table()
{
   tableTop = new TableTop();
   tableSides = new TableSides();

   double w = Config::getTableWidth() / 2.0;
   double d = Config::getTableDepth() / 2.0;
   
   pockets.push_back(new CornerPocket(Vec3f(-w, 1, -d)));
   pockets.push_back(new CornerPocket(Vec3f(-w, 1, d)));
   pockets.push_back(new CornerPocket(Vec3f(w, 1, d)));
   pockets.push_back(new CornerPocket(Vec3f(w, 1, -d)));
   pockets.push_back(new SidePocket(Vec3f(-w, 1, 0)));
   pockets.push_back(new SidePocket(Vec3f(w, 1, 0)));
}

Table::~Table()
{
   if (tableTop) delete tableTop;
   if (tableSides) delete tableSides;
}

void Table::draw(bool ortho)
{
	glPushName(1);
   tableSides->draw(ortho);
   tableTop->draw(ortho);
   for (int i = 0; i < pockets.size(); i++)
   {
      pockets[i]->draw(ortho);
   }

   glPopName();
}

vector<Pocket*> Table::getPockets()
{
   return pockets;
}
void Table::drawShadow(Vec3f, Vec3f, int)
{

}

Scene::Scene() : angle(0)
{

   table = new Table();

   cueBall = new CueBall();
   
   for (int i = 0; i < 15; i++) 
   {
      playingBalls[i] = new Ball();
   }

   stick = new Stick(cueBall);
   
   
   room = new Room();
   
   light = new Light();
   light->addShadowObject(cueBall);
   light->addShadowObject(stick);
   light2 = new Light();
   light2->addShadowObject(cueBall);
   light2->addShadowObject(stick);
   light3 = new Light();
   light3->addShadowObject(cueBall);
   light3->addShadowObject(stick);
   for (int i = 0; i < 15; i++) 
   {
      light->addShadowObject(playingBalls[i]);
      light2->addShadowObject(playingBalls[i]);
      light3->addShadowObject(playingBalls[i]);
   }
  
   setup();
}

void Scene::setup()
{
   
   light3->setAmbience(Vec3f(.15, .15, .15));
   light3->setPosition(Vec3f(0, 4000, -3000));
   light3->setMode(Light::SPOT);
   light3->setSpotExponent(10);
   light3->setDirection(Vec3f(0,-1,0));
   light3->setSpotAngle(90);
   light3->setDiffuse(Vec3f(0.6,0.6,0.6));
   light3->setSpecular(Vec3f(0.7,0.7,0.7));
   
   light->setAmbience(Vec3f(.10, .10, .10));
   light->setPosition(Vec3f(0, 4000, 0));
   light->setMode(Light::SPOT);
   light->setSpotExponent(8);
   light->setDirection(Vec3f(0,-1,0));
   light->setSpotAngle(90);
   light->setDiffuse(Vec3f(0.6,0.6,0.6));
   light->setSpecular(Vec3f(0.7,0.7,0.7));
   
   light2->setAmbience(Vec3f(.15, .15, .15));
   light2->setPosition(Vec3f(0, 4000, 3000));
   light2->setMode(Light::SPOT);
   light2->setSpotExponent(10);
   light2->setDirection(Vec3f(0,-1,0));
   light2->setSpotAngle(90);
   light2->setDiffuse(Vec3f(0.6,0.6,0.6));
   light2->setSpecular(Vec3f(0.7,0.7,0.7));


   cueBall->setOrigin(Vec3f(0, 0, Config::getTableDepth() / 4.0));
   center = cueBall->getOrigin();
   
   // Origin for playing balls
   Vec3f o = Vec3f(0,0, Config::getTableDepth() / -4.0);
   
   // Setup balls in triangle
   int ballNum = 0;
   double diameter = playingBalls[0]->getDiameter() * 1.05;
   for (int row = 0; row < 5; row++)
   {
      for (int i = 0; i <= row; i++)
      {
         //Vec3f v = Vec3f(-5000, 0, -5000);
         
         
         Vec3f v = Vec3f(o[0] + i * diameter - ((row / 2.0) * diameter), o[1], o[2] - row * diameter * .85);
         
//         if (row == 0 && i == 0)
//            v = Vec3f(0, 0, 2000);
         
         playingBalls[ballNum]->setOrigin(v);
         ballNum++;
      }
   }
}

Scene::~Scene() 
{
   if (cueBall) delete cueBall;
   if (playingBalls) delete [] playingBalls;
   if (stick) delete stick;
   if (table) delete table;
   if (room) delete room;
   if (light) delete light;
   if (light2) delete light2;
   if (light3) delete light2;
}

void Scene::draw(bool ortho, bool movingBalls)
{   

   
   glPushMatrix();
      
   if (!movingBalls)
      center = cueBall->getOrigin();

   if (!ortho)
   {
      // Change to correct viewing angle
      glTranslatef(center[0], center[1], center[2]);
      glRotatef(-angle, 0, 1, 0);
      glTranslatef(-center[0], -center[1], -center[2]);      
   } 


   room->draw(ortho);
   table->draw(ortho);
   
   cueBall->draw(ortho);

   for (int i = 0; i < 15; i++) 
   {
      playingBalls[i]->draw(ortho);
   }
   
   light->draw();
   light2->draw();
   light3->draw();
   
   stick->draw(ortho,  movingBalls);
   glPopMatrix();
}

CueBall* Scene::getCueBall()
{
   return cueBall;
}

Ball** Scene::getPlayingBalls()
{
   return playingBalls;
}

Table* Scene::getTable()
{
   return table;
}

Stick* Scene::getStick()
{
   return stick;
}

double Scene::getAngle()
{
   return angle;
}

void Scene::setAngle(double _angle)
{
   int n = (int)(_angle / 360);
   angle = _angle - (n * 360);
   
   // Change Cue stick to new angle
   stick->setAngle(angle);
}

void Scene::changeAngleBy(double v) 
{
   setAngle(angle + v);
}


